<!-----------------------------------------------------------------------------
-- Spine Runtimes Software License
-- Version 2.1
-- 
-- Copyright (c) 2013, Esoteric Software
-- All rights reserved.
-- 
-- You are granted a perpetual, non-exclusive, non-sublicensable and
-- non-transferable license to install, execute and perform the Spine Runtimes
-- Software (the "Software") solely for internal use. Without the written
-- permission of Esoteric Software (typically granted by licensing Spine), you
-- may not (a) modify, translate, adapt or otherwise create derivative works,
-- improvements of the Software or develop new applications using the Software
-- or (b) remove, delete, alter or obscure any trademarks or any copyright,
-- trademark, patent or other intellectual property or proprietary rights
-- notices on or in the Software, including any copy thereof. Redistributions
-- in binary or source form must include this license and terms.
-- 
-- THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
-- IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-- EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-- OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-- WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-- OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
------------------------------------------------------------------------------>

<html>
<head>
<meta charset="UTF-8">
<title>spine-threejs</title>
<script src="../spine-js/spine.js"></script>
<script src="../three.min.js"></script>

<style>body, input { font-family: tahoma; font-size: 11pt }</style>
</head>
<body>

<script>
(function () {

	var loadText = function (url, callback) {
		var req = new XMLHttpRequest();
		req.open("GET", url, true);
		req.responseType = 'text';
		req.addEventListener('error', function (event) {}, false);
		req.addEventListener('abort', function (event) {}, false);
		req.addEventListener('load', function (event) { callback(req.response); }, false);
		req.send();
		return req;
	};

	var loadImage = function (url, callback) {
		var image = new Image();
		image.addEventListener('error', function (event) {}, false);
		image.addEventListener('abort', function (event) {}, false);
		image.addEventListener('load', function (event) { callback (image); }, false);
		image.src = url;
		return image;
	};

	SpineAnimation = function (name, path, scale) {

		THREE.Object3D.call(this);

		this.name = name;

		path = path ? (path +
			((path.substr(-1) != '/') ? '/' : '')
		) : '';

		var self = this;

		loadText(path + name + '.atlas', function (atlasText) {
			self.atlas = new spine.Atlas(atlasText, {
				load: function (page, image, atlas) {
					loadImage(path + image, function (image) {
						// calculate UVs in atlas regions
						page.width = image.width;
						page.height = image.height;

						atlas.updateUVs(page);

						// propagate new UVs to attachments, if they were already created
						if (self.skeleton) {
							var skins = self.skeleton.data.skins;
							for (var s = 0, n = skins.length; s < n; s++) {
								var attachments = skins[s].attachments;
								for (var k in attachments) {
									var attachment = attachments[k];
									if (attachment instanceof spine.RegionAttachment) {
										var region = attachment.rendererObject;
										attachment.setUVs(region.u, region.v, region.u2, region.v2, region.rotate);
									}
								}
							}
						}

						// create basic material for the page
						var texture = new THREE.Texture(image);
						texture.needsUpdate = true;

						page.rendererObject = [
							new THREE.MeshBasicMaterial({
								//color: 0xff00, wireframe: true,
								map : texture, side : THREE.DoubleSide, transparent : true, alphaTest : 0.5
							})
						];
					});
				},
				unload: function (materials) {
					for (var i = 0, n = materials.length; i < n; i++) {
						var material = materials[i];
						if (material.meshes) {
							for (var name in material.meshes) {
								var mesh = material.meshes[name];
								if (mesh.parent) mesh.parent.remove(mesh);
								mesh.geometry.dispose();
							}
						}
						material.map.dispose();
						material.dispose();
					}
					// will be called multiple times
					materials.length = 0;
				}
			});

			loadText(path + name + '.json', function (skeletonText) {
				var json = new spine.SkeletonJson(new spine.AtlasAttachmentLoader(self.atlas));
				json.scale = scale || 1;

				var skeletonData = json.readSkeletonData(JSON.parse(skeletonText));

				self.skeleton = new spine.Skeleton(skeletonData);
				self.stateData = new spine.AnimationStateData(skeletonData);	
				self.state = new spine.AnimationState(self.stateData);

				self.dispatchEvent({
					type : SpineAnimation.SKELETON_DATA_LOADED
				});
			});
		});

		var matrix = new THREE.Matrix4();

		// if given, dt must be in ms

		this.update = function (dt, dz) {
			if (!this.state) return;

			this.state.update(dt || (1.0 / 60));
			this.state.apply(this.skeleton);
			this.skeleton.updateWorldTransform();

			this.traverse(function (object) {
				if (object instanceof THREE.Mesh) {
					object.visible = false;
				}
			});

			var Z = 0;
			var drawOrder = this.skeleton.drawOrder;
			for (var i = 0, n = drawOrder.length; i < n; i++) {
				var slot = drawOrder[i];
				var attachment = slot.attachment;
				if (!(attachment instanceof spine.RegionAttachment)) continue;

				var materials = attachment.rendererObject.page.rendererObject;
				// texture was not loaded yet
				if (!materials) continue;

				if (slot.data.additiveBlending && (materials.length == 1)) {
					// create separate material for additive blending
					materials.push(new THREE.MeshBasicMaterial({
						map : materials[0].map,
						side : THREE.DoubleSide,
						transparent : true,
						blending : THREE.AdditiveBlending,
						depthWrite : false
					}));
				}

				var material = materials[ slot.data.additiveBlending ? 1 : 0 ];

				material.meshes = material.meshes || {};

				var mesh = material.meshes[slot.data.name];

				var geometry;

				if (mesh) {
					geometry = mesh.geometry;

					mesh.visible = true;
				} else {
					geometry = new THREE.PlaneGeometry(
						attachment.regionOriginalWidth,
						attachment.regionOriginalHeight
					);
					geometry.dynamic = true;

					mesh = new THREE.Mesh(geometry, material);
					mesh.matrixAutoUpdate = false;

					material.meshes[slot.data.name] = mesh;
					this.add(mesh);
				}

				if (mesh.attachmentTime && (slot.getAttachmentTime () > mesh.attachmentTime)) {
					// do nothing
				} else {
					// update UVs
					geometry.faceVertexUvs[0][0][0].set(attachment.uvs[6], 1- attachment.uvs[7]);
					geometry.faceVertexUvs[0][0][1].set(attachment.uvs[4], 1- attachment.uvs[5]);
					geometry.faceVertexUvs[0][0][2].set(attachment.uvs[0], 1- attachment.uvs[1]);
					geometry.faceVertexUvs[0][1][0].set(attachment.uvs[4], 1- attachment.uvs[5]);
					geometry.faceVertexUvs[0][1][1].set(attachment.uvs[2], 1- attachment.uvs[3]);
					geometry.faceVertexUvs[0][1][2].set(attachment.uvs[0], 1- attachment.uvs[1]);
					geometry.uvsNeedUpdate = true;

					geometry.vertices[1].set(attachment.offset[0], attachment.offset[1], 0);
					geometry.vertices[3].set(attachment.offset[2], attachment.offset[3], 0);
					geometry.vertices[2].set(attachment.offset[4], attachment.offset[5], 0);
					geometry.vertices[0].set(attachment.offset[6], attachment.offset[7], 0);
					geometry.verticesNeedUpdate = true;

					mesh.attachmentTime = slot.getAttachmentTime();
				}

				matrix.makeTranslation(
					this.skeleton.x + slot.bone.worldX,
					this.skeleton.y + slot.bone.worldY,
					(dz || 0.1) * Z++
				);

				matrix.elements[0] = slot.bone.a; matrix.elements[4] = slot.bone.b;
				matrix.elements[1] = slot.bone.c; matrix.elements[5] = slot.bone.d;

				mesh.matrix.copy(matrix);

				/* TODO slot.r,g,b,a ?? turbulenz example code:
				batch.add(
					attachment.rendererObject.page.rendererObject,
					vertices[0], vertices[1],
					vertices[6], vertices[7],
					vertices[2], vertices[3],
					vertices[4], vertices[5],
					skeleton.r * slot.r,
					skeleton.g * slot.g,
					skeleton.b * slot.b,
					skeleton.a * slot.a,
					attachment.uvs[0], attachment.uvs[1],
					attachment.uvs[4], attachment.uvs[5]
				);
				*/
			}

		};
	};

	SpineAnimation.SKELETON_DATA_LOADED = 'skeletonDataLoaded';

	SpineAnimation.prototype = Object.create(THREE.Object3D.prototype);

	SpineAnimation.prototype.dispose = function () {
		if (this.parent) this.parent.remove(this); this.atlas.dispose();
	};

}) ();

var scene, camera, renderer;
var geometry, material, mesh;
var anim;

function load (name, scale) {
	if (anim) anim.dispose();

	anim = new SpineAnimation(name, 'data/', scale);

	anim.addEventListener(SpineAnimation.SKELETON_DATA_LOADED, function () {
		var canvas = renderer.domElement;

		switch (anim.name) {
		case 'spineboy':
			anim.stateData.setMixByName('walk', 'jump', 0.2);
			anim.stateData.setMixByName('run', 'jump', 0.2);
			anim.stateData.setMixByName('jump', 'run', 0.2);
			anim.state.setAnimationByName(0, 'walk', true);
			canvas.onmousedown = function () {
				anim.state.setAnimationByName(0, 'jump', false);
				anim.state.addAnimationByName(0, 'run', true, 0);
			}
			break;
		case 'hero':
			anim.stateData.defaultMix = 0.2;
			anim.stateData.setMixByName('Walk', 'Attack', 0);
			anim.stateData.setMixByName('Attack', 'Run', 0);
			anim.stateData.setMixByName('Run', 'Attack', 0);
			anim.state.setAnimationByName(0, 'Idle', true);
			canvas.onmousedown = function () {
				var name = anim.state.getCurrent(0).animation.name;
				if (name == 'Idle')
					anim.state.setAnimationByName(0, 'Crouch', true);
				else if (name == 'Crouch')
					anim.state.setAnimationByName(0, 'Walk', true);
				else {
					anim.state.setAnimationByName(0, 'Attack', false);
					anim.state.addAnimationByName(0, 'Run', true, 0);
				}
			}
			break;
		case 'goblins':
			anim.skeleton.setSkinByName('goblingirl'); // TODO - spine.Skeleton.prototype.setSkin doesn't work?
			anim.skeleton.setSlotsToSetupPose();
			anim.state.setAnimationByName(0, 'walk', true);
			canvas.onmousedown = function () {
				anim.skeleton.setSkinByName(anim.skeleton.skin.name == 'goblin' ? 'goblingirl' : 'goblin');
				anim.skeleton.setSlotsToSetupPose();
			}
			break;
		case 'skeleton':
			anim.state.setAnimationByName(0, 'walk test', true);
			canvas.onmousedown = function () {
				var name = anim.state.getCurrent(0).animation.name;
				if (name == 'walk test')
					anim.state.setAnimationByName(0, 'idle test', true);
				else
					anim.state.setAnimationByName(0, 'walk test', true);
			}
			break;
		}
		//anim.skeleton.y = -200;
	});

	mesh.add(anim);
}

function init() {
	scene = new THREE.Scene();

	var width = 640, height = 480;
	camera = new THREE.PerspectiveCamera(75, width / height, 1, 3000);
	camera.position.z = 400;

	geometry = new THREE.BoxGeometry(200, 200, 200);
	material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true });

	mesh = new THREE.Mesh(geometry, material);
	scene.add(mesh);

	load('spineboy', 0.4);

	renderer = new THREE.WebGLRenderer();
	renderer.setSize(width, height);

	document.body.appendChild(renderer.domElement);
}

var lastTime = Date.now();
function animate() {
	requestAnimationFrame(animate);

	var t = Date.now();
	var a = Math.sin(t * 6e-4);
	var b = Math.cos(t * 3e-4);

	mesh.rotation.x = a * Math.PI * 0.2;
	mesh.rotation.y = b * Math.PI * 0.4;

	anim.update((t - lastTime) / 1000);
	lastTime = t;

	renderer.render(scene, camera);
}

init();
animate();
</script>

<br><br>
<input type="button" value="Spineboy" onclick="load('spineboy', 0.6)">
<input type="button" value="Hero" onclick="load('hero', 1)">
<input type="button" value="Goblins" onclick="load('goblins', 1)">
 &nbsp; &nbsp; Click to change the animation or skin.

</body>
</html>